package userInterface.Panels;

import dataAccess.*;
import logging.*;
import settings.*;
import settings.Action.Action;
import userInterface.Panels.CommandArgumentPanels.*;

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;

/**
 * {@link userInterface.panels.PanelBase PanelBase} for editing {@link settings.Command Commands}.
 * @author BDS
 */
public class PanelCommands extends PanelBase
{
	private static final long serialVersionUID = 1L;

	private Environment currentEnvironment;
	private Workflow currentWorkflow;
	private Command currentCommand;
	private Command copiedCommand;

	private ActionListener commandActionListener;
	private ListSelectionListener commandListSelectionListener;
	private TransferHandler commandTransferHandler;
	private ActionListener commandArgumentsActionListener;
	private FocusAdapter commandArgumentsFocusAdapter;
	private KeyAdapter commandArgumentsKeyAdapter;
	private ChangeListener commandArgumentsChangeListener;
	
	private GridBagLayout gblCommand;
	
	private JLabel lblEnvironment;
    private DefaultComboBoxModel<Environment> environments;
    private JComboBox<Environment> cbxEnvironments;
	private JLabel lblWorkflow;
	private DefaultComboBoxModel<Workflow> workflows;
	private JComboBox<Workflow> cbxWorkflows;
	
	private JButton btnAdd;
	private JButton btnRemove;
	private JButton btnCopy;
	private JButton btnPaste;
	private DefaultListModel<Command> commands;
	private JList<Command> lstCommands;
	private JScrollPane scpCommands;
	
	private JLabel lblAction;
	private JComboBox<Action> cbxActions;
	private PanelCommandArguments pnlCommandArguments;
	private JLabel lblDescription;
	private JTextArea txtDescription;
	
	private JButton btnSave;
	private JButton btnRevert;

	/**
	 * Class constructor.
	 */
	public PanelCommands()
	{
		commandActionListener = new CommandActionListener();
		commandListSelectionListener = new CommandListSelectionListener();
		commandTransferHandler = new CommandTransferHandler();
		commandArgumentsActionListener = new CommandArgumentsActionListener();
		commandArgumentsFocusAdapter = new CommandArgumentsFocusAdapter();
		commandArgumentsKeyAdapter = new CommandArgumentsKeyAdapter();
		commandArgumentsChangeListener = new CommandArgumentsChangeListener();
		
		gblCommand = new GridBagLayout();
		gblCommand.columnWidths = new int[] { 100, 0, 0, 0, 100, 0, 0, 0 };
		gblCommand.columnWeights = new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0 };
		gblCommand.rowWeights = new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 };
		setLayout(gblCommand);

		lblEnvironment = new JLabel("Environment:");
        GridBagConstraints gbc_lblEnvironment = new GridBagConstraints();
        gbc_lblEnvironment.anchor = GridBagConstraints.WEST;
        gbc_lblEnvironment.insets = new Insets(10, 10, 5, 5);
        gbc_lblEnvironment.gridwidth = 2;
        gbc_lblEnvironment.gridx = 0;
        gbc_lblEnvironment.gridy = 0;
        add(lblEnvironment, gbc_lblEnvironment);

        environments = new DefaultComboBoxModel<Environment>();
        cbxEnvironments = new JComboBox<Environment>(environments);
        cbxEnvironments.setActionCommand("Environment changed");
        cbxEnvironments.addActionListener(commandActionListener);
        GridBagConstraints gbc_cbxEnvironments = new GridBagConstraints();
        gbc_cbxEnvironments.fill = GridBagConstraints.HORIZONTAL;
        gbc_cbxEnvironments.insets = new Insets(10, 0, 5, 10);
        gbc_cbxEnvironments.gridwidth = 7;
        gbc_cbxEnvironments.gridx = 1;
        gbc_cbxEnvironments.gridy = 0;
        add(cbxEnvironments, gbc_cbxEnvironments);
		
		lblWorkflow = new JLabel("Workflow:");
		lblWorkflow.setEnabled(false);
		GridBagConstraints gbc_lblWorkflow = new GridBagConstraints();
		gbc_lblWorkflow.anchor = GridBagConstraints.WEST;
        gbc_lblWorkflow.insets = new Insets(0, 10, 5, 5);
        gbc_lblWorkflow.gridwidth = 2;
        gbc_lblWorkflow.gridx = 0;
        gbc_lblWorkflow.gridy = 1;
        add(lblWorkflow, gbc_lblWorkflow);
		
		workflows = new DefaultComboBoxModel<Workflow>();
		cbxWorkflows = new JComboBox<Workflow>(workflows);
		cbxWorkflows.setEnabled(false);
		cbxWorkflows.setActionCommand("Workflow changed");
		cbxWorkflows.addActionListener(commandActionListener);
		GridBagConstraints gbc_cbxWorkflows = new GridBagConstraints();
		gbc_cbxWorkflows.fill = GridBagConstraints.HORIZONTAL;
		gbc_cbxWorkflows.insets = new Insets(0, 0, 5, 10);
		gbc_cbxWorkflows.gridwidth = 7;
		gbc_cbxWorkflows.gridx = 1;
		gbc_cbxWorkflows.gridy = 1;
        add(cbxWorkflows, gbc_cbxWorkflows);
		
		btnAdd = new JButton();
		btnAdd.setIcon(new ImageIcon(getClass().getResource("/images/add.png")));
		btnAdd.setToolTipText("Add a new command");
		btnAdd.setEnabled(false);
		btnAdd.setMnemonic(KeyEvent.VK_N);
		btnAdd.setActionCommand("Add");
		btnAdd.addActionListener(commandActionListener);
		GridBagConstraints gbc_btnAdd = new GridBagConstraints();
		gbc_btnAdd.anchor = GridBagConstraints.WEST;
		gbc_btnAdd.insets = new Insets(0, 10, 5, 30);
		gbc_btnAdd.gridx = 0;
		gbc_btnAdd.gridy = 2;
        add(btnAdd, gbc_btnAdd);

		btnRemove = new JButton();
		btnRemove.setIcon(new ImageIcon(getClass().getResource("/images/remove.png")));
		btnRemove.setToolTipText("Remove the selected command");
		btnRemove.setEnabled(false);
		btnRemove.setMnemonic(KeyEvent.VK_E);
		btnRemove.setActionCommand("Remove");
		btnRemove.addActionListener(commandActionListener);
		GridBagConstraints gbc_btnRemove = new GridBagConstraints();
		gbc_btnRemove.anchor = GridBagConstraints.WEST;
		gbc_btnRemove.insets = new Insets(0, 0, 5, 30);
		gbc_btnRemove.gridx = 1;
		gbc_btnRemove.gridy = 2;
        add(btnRemove, gbc_btnRemove);

		btnCopy = new JButton();
		btnCopy.setIcon(new ImageIcon(getClass().getResource("/images/copy.png")));
		btnCopy.setToolTipText("Copy the selected command");
		btnCopy.setEnabled(false);
		btnCopy.setMnemonic(KeyEvent.VK_C);
		btnCopy.setActionCommand("Copy");
		btnCopy.addActionListener(commandActionListener);
		GridBagConstraints gbc_btnCopy = new GridBagConstraints();
		gbc_btnCopy.anchor = GridBagConstraints.WEST;
        gbc_btnCopy.insets = new Insets(0, 0, 5, 30);
        gbc_btnCopy.gridx = 2;
        gbc_btnCopy.gridy = 2;
        add(btnCopy, gbc_btnCopy);
		
		btnPaste = new JButton();
		btnPaste.setIcon(new ImageIcon(getClass().getResource("/images/paste.png")));
		btnPaste.setToolTipText("Paste the copied command");
		btnPaste.setEnabled(false);
		btnPaste.setMnemonic(KeyEvent.VK_P);
		btnPaste.setActionCommand("Paste");
		btnPaste.addActionListener(commandActionListener);
		GridBagConstraints gbc_btnPaste = new GridBagConstraints();
		gbc_btnPaste.anchor = GridBagConstraints.WEST;
		gbc_btnPaste.insets = new Insets(0, 0, 5, 5);
		gbc_btnPaste.gridx = 3;
        gbc_btnPaste.gridy = 2;
        add(btnPaste, gbc_btnPaste);

		commands = new DefaultListModel<Command>();
		lstCommands = new JList<Command>(commands)
		{
			private static final long serialVersionUID = 1L;

			/**
			 * Utilizes the associated {@link settings.Command Command's} description for a tooltip.
			 * @param mouseEvent The triggering {@link java.awt.event.MouseEvent MouseEvent}.
			 * @returns A tooltip representing the associated {@link settings.Command Command's} description.
			 */
			@Override
			public String getToolTipText(MouseEvent mouseEvent) 
			{
				String toolTip = null;
				
				if (locationToIndex(mouseEvent.getPoint()) != -1)
				{
					toolTip = getModel().getElementAt(locationToIndex(mouseEvent.getPoint())).getDetails();
				}
				
				return toolTip == null || toolTip.isEmpty() ? null : toolTip;
		    }
		};
		lstCommands.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		lstCommands.setDragEnabled(true);
		lstCommands.setDropMode(DropMode.INSERT);
		lstCommands.addListSelectionListener(commandListSelectionListener);
		lstCommands.setTransferHandler(commandTransferHandler);
		scpCommands = new JScrollPane(lstCommands);
		GridBagConstraints gbc_scpCommands = new GridBagConstraints();
		gbc_scpCommands.fill = GridBagConstraints.BOTH;
		gbc_scpCommands.insets = new Insets(0, 10, 10, 5);
		gbc_scpCommands.gridwidth = 4;
        gbc_scpCommands.gridheight = 6;
		gbc_scpCommands.gridx = 0;
		gbc_scpCommands.gridy = 3;
        add(scpCommands, gbc_scpCommands);
		
		lblAction = new JLabel("Action:");
		lblAction.setEnabled(false);
		GridBagConstraints gbc_lblAction = new GridBagConstraints();
		gbc_lblAction.anchor = GridBagConstraints.WEST;
		gbc_lblAction.insets = new Insets(0, 0, 5, 5);
		gbc_lblAction.gridx = 4;
		gbc_lblAction.gridy = 3;
        add(lblAction, gbc_lblAction);

		cbxActions = new JComboBox<Action>(Action.values());
		cbxActions.setActionCommand("Action changed");
		cbxActions.addActionListener(commandActionListener);
		cbxActions.setEnabled(false);
		GridBagConstraints gbc_cbxActions = new GridBagConstraints();
		gbc_cbxActions.fill = GridBagConstraints.HORIZONTAL;
		gbc_cbxActions.insets = new Insets(0, 0, 5, 10);
		gbc_cbxActions.gridwidth = 3;
		gbc_cbxActions.gridx = 5;
		gbc_cbxActions.gridy = 3;
        add(cbxActions, gbc_cbxActions);
		
		pnlCommandArguments = new PanelNoArguments();
        GridBagConstraints gbc_pnlCommandArguments = new GridBagConstraints();
        gbc_pnlCommandArguments.fill = GridBagConstraints.BOTH;
        gbc_pnlCommandArguments.insets = new Insets(0, 0, 5, 10);
        gbc_pnlCommandArguments.gridwidth = 4;
        gbc_pnlCommandArguments.gridx = 4;
        gbc_pnlCommandArguments.gridy = 4;
        add(pnlCommandArguments, gbc_pnlCommandArguments);
		
		lblDescription = new JLabel("Description:");
		lblDescription.setEnabled(false);
		GridBagConstraints gbc_lblDescription = new GridBagConstraints();
		gbc_lblDescription.anchor = GridBagConstraints.WEST;
		gbc_lblDescription.insets = new Insets(0, 0, 5, 10);
		gbc_lblDescription.gridwidth = 3;
		gbc_lblDescription.gridx = 4;
		gbc_lblDescription.gridy = 5;
        add(lblDescription, gbc_lblDescription);

		txtDescription = new JTextArea(3, 0);
		txtDescription.setBorder(new JTextField().getBorder());
		txtDescription.setEnabled(false);
		txtDescription.addFocusListener(commandArgumentsFocusAdapter);
		txtDescription.addKeyListener(commandArgumentsKeyAdapter);
		GridBagConstraints gbc_txtDescription = new GridBagConstraints();
		gbc_txtDescription.fill = GridBagConstraints.BOTH;
		gbc_txtDescription.insets = new Insets(0, 0, 5, 10);
		gbc_txtDescription.gridwidth = 4;
		gbc_txtDescription.gridx = 4;
		gbc_txtDescription.gridy = 6;
        add(txtDescription, gbc_txtDescription);
		
		btnSave = new JButton("Save");
		btnSave.setMnemonic(KeyEvent.VK_S);
		btnSave.addActionListener(commandActionListener);
		GridBagConstraints gbc_btnSave = new GridBagConstraints();
		gbc_btnSave.anchor = GridBagConstraints.EAST;
		gbc_btnSave.insets = new Insets(0, 0, 10, 5);
		gbc_btnSave.gridx = 6;
		gbc_btnSave.gridy = 8;
        add(btnSave, gbc_btnSave);
		
		btnRevert = new JButton("Revert");
		btnRevert.setMnemonic(KeyEvent.VK_R);
		btnRevert.addActionListener(commandActionListener);
		GridBagConstraints gbc_btnRevert = new GridBagConstraints();
		gbc_btnRevert.anchor = GridBagConstraints.EAST;
		gbc_btnRevert.insets = new Insets(0, 0, 10, 10);
		gbc_btnRevert.gridx = 7;
        gbc_btnRevert.gridy = 8;
        add(btnRevert, gbc_btnRevert);

		setEditedState(false);
	}

	/**
	 * Prompts the user to save any pending changes.
	 */
	@Override
	public void promptSaveChanges()
	{
		if (btnSave.isEnabled())
		{
			if (JOptionPane.showConfirmDialog(null,
											  "Save command changes?",
											  "Commands",
											  JOptionPane.YES_NO_OPTION,
											  JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION)
			{
				save();
			}
		}
		
		setEditedState(false);
	}
	
    /**
     * Sets the enabled state of contained controls.
     * @param enabled Determines the enabled state of contained controls.
     */
	@Override
	public void setEnabled(boolean enabled)
	{
		btnRemove.setEnabled(enabled);
		btnCopy.setEnabled(enabled);
		
		lblAction.setEnabled(enabled);
		cbxActions.setEnabled(enabled);
		pnlCommandArguments.setEnabled(enabled);
		lblDescription.setEnabled(enabled);
		txtDescription.setEnabled(enabled);
	}
	
	/**
	 * Saves all {@link settings.Command Commands} for the current {@link settings.Workflow Workflow}.
	 */
	private void save()
	{
		setEditedState(false);
		
		Settings settings = LocalDataAccess.loadSettings();

		List<Environment> environments = settings.getEnvironments();
		environments.set(environments.indexOf(currentEnvironment), currentEnvironment);
		settings.setEnvironments(environments);

		LocalDataAccess.saveSettings(settings);
		resetFocus();
	}

	/**
	 * Loads all saved {@link settings.Environment Environments} and consequently all saved {@link settings.Workflow Workflows}
	 * and {@link settings.Command Commands}.
	 */
	@Override
	public void load()
	{
		setEditedState(false);
		
		environments.removeAllElements();
		for (Environment environment : LocalDataAccess.loadSettings().getEnvironments())
		{
			environments.addElement(environment);
		}
		environments.setSelectedItem(null);
		
		workflows.removeAllElements();
		workflows.setSelectedItem(null);
		
		commands.clear();
		lstCommands.setSelectedIndex(-1);
		
		currentEnvironment = null;
		currentWorkflow = null;
		currentCommand = null;
		
		copiedCommand = null;
		btnPaste.setEnabled(false);
		btnPaste.setToolTipText("Paste the copied command");
		
		resetFocus();
	}
	
	/**
	 * Sets the availability of the Save and Revert {@link javax.swing.JButton JButtons} based on the given state.
	 * @param edited <ul>
     *                   <li><code>true</code> if any {@link settings.Command Commands} do not match the settings.txt file.</li>
     *                   <li><code>false</code> if all {@link settings.Command Commands} match the settings.txt file.</li>
     *               </ul>
	 */
	private void setEditedState(boolean edited)
	{
		btnSave.setEnabled(edited);
		btnRevert.setEnabled(edited);
	}

	/**
	 * Updates the selected {@link settings.Command Command} to reflect what the UI currently displays.
	 * If no {@link settings.Command Command} is selected, the UI will be updated to reflect this.
	 */
	private void updateCommand()
	{
		if (currentCommand == null || currentWorkflow == null || currentEnvironment == null)
		{
			cbxActions.setSelectedIndex(0);
			txtDescription.setText("");

			// Ensure that a recently disabled element is not focused.
			resetFocus();
		}
		else if (commands.contains(currentCommand))
		{
			if (currentCommand.getAction() != (Action) cbxActions.getSelectedItem() || 
				!currentCommand.getArguments().equals(pnlCommandArguments.getArguments()))
			{
				setEditedState(true);
			}
			
			currentCommand.setAction((Action) cbxActions.getSelectedItem());
			currentCommand.setArguments(pnlCommandArguments.getArguments());
			currentCommand.setDescription(txtDescription.getText());
			
			commands.set(commands.indexOf(currentCommand), currentCommand);

			// DefaultListModel does not support toArray(T[]).
			Command[] newCommands = new Command[commands.getSize()];
			commands.copyInto(newCommands);
			currentWorkflow.setCommands(new ArrayList<Command>(Arrays.asList(newCommands)));
			
			List<Workflow> workflows = currentEnvironment.getWorkflows();
			if (workflows.contains(currentWorkflow))
			{
				workflows.set(workflows.indexOf(currentWorkflow), currentWorkflow);
				currentEnvironment.setWorkflows(workflows);
			}
		}
		
		currentCommand = lstCommands.getSelectedValue();

		setEnabled(currentCommand != null);
		btnAdd.setEnabled(currentWorkflow != null);
		
		if (currentCommand != null)
		{
			btnRemove.setToolTipText("Remove the " + currentCommand.toString() + " command");
			btnCopy.setToolTipText("Copy the " + currentCommand.toString() + " command");
		}
		else
		{
			btnRemove.setToolTipText("Remove the selected command");
			btnCopy.setToolTipText("Copy the selected command");
		}
	}

	/**
	 * Updates the {@link userInterface.panels.CommandArgumentPanels.PanelCommandArguments PanelCommandArguments} based on the selected {@link settings.Command Command}.
	 */
	private void updateCommandArgumentsPanel()
	{
		remove(pnlCommandArguments);

		if (cbxActions.getSelectedIndex() == -1)
		{
			pnlCommandArguments = new PanelNoArguments();
		}
		else
		{
			switch ((Action) cbxActions.getSelectedItem())
			{
			case PAUSE:
				pnlCommandArguments = new PanelPauseArguments();
				break;
			case KEYSTROKE:
				pnlCommandArguments = new PanelKeystrokeArguments();
				break;
			case MOUSEMOVE:
				pnlCommandArguments = new PanelMouseMoveArguments();
				break;
			case MOUSECLICK:
				pnlCommandArguments = new PanelNoArguments();
				break;
			case TEXTENTRY:
				pnlCommandArguments = new PanelTextEntryArguments();
				break;
			case LOG:
				pnlCommandArguments = new PanelLogArguments();
				break;
			case VALIDATE:
				pnlCommandArguments = new PanelValidateArguments();
				break;
			}
		}

		if (currentCommand != null)
		{
			pnlCommandArguments.setArguments(currentCommand.getArguments());
			txtDescription.setText(currentCommand.getDescription());
		}
		
		pnlCommandArguments.addActionListeners(commandArgumentsActionListener, 
											   commandArgumentsFocusAdapter, 
											   commandArgumentsKeyAdapter, 
											   commandArgumentsChangeListener);
		GridBagConstraints gbc_pnlCommandArguments = new GridBagConstraints();
        gbc_pnlCommandArguments.fill = GridBagConstraints.BOTH;
        gbc_pnlCommandArguments.insets = new Insets(0, 0, 5, 10);
        gbc_pnlCommandArguments.gridwidth = 4;
        gbc_pnlCommandArguments.gridx = 4;
        gbc_pnlCommandArguments.gridy = 4;
        add(pnlCommandArguments, gbc_pnlCommandArguments);
        
		validate();
	}

	/**
	 * {@link java.awt.event.ActionListener ActionListener} for the following {@link userInterface.panels.PanelCommands PanelCommands} items:
	 * <ul>
	 *     <li>{@link settings.Environment Environment} {@link javax.swing.JComboBox JComboBox}</li>
     *     <li>{@link settings.Workflow Workflow} {@link javax.swing.JComboBox JComboBox}</li>
     *     <li>{@link settings.Command Command} {@link javax.swing.JButton JButtons}</li>
	 * </ul>
	 * @author BDS
	 */
	private class CommandActionListener implements ActionListener
	{
		/**
		 * Executes specific logic based on the following {@link java.awt.event.ActionEvent ActionEvents}:
		 * <ul>
         *     <li>Environment changed - Loads the {@link settings.Workflow Workflows} for the selected {@link settings.Environment Environment}.</li>
         *     <li>Workflow changed - Loads the {@link settings.Command Commands} for the selected {@link settings.Workflow Workflow}.</li>
         *     <li>Add - Creates and adds a new {@link settings.Command Command}.</li>
         *     <li>Remove - Removes the selected {@link settings.Command Command}.</li>
         *     <li>Copy - Copies the selected {@link settings.Command Command} to the pseudo clipboard.</li>
         *     <li>Paste - Inserts the copied {@link settings.Command Command} from the pseudo clipboard.</li>
         *     <li>Action changed - Updates the displayed {@link userInterface.panels.CommandArgumentPanels.PanelCommandArguments PanelCommandArguments}.</li>
         *     <li>Save - Saves pending {@link settings.Command Command} changes for the current {@link settings.Workflow Workflow}.</li>
         *     <li>Revert - Reverts the selected {@link settings.Workflow Workflow's} {@link settings.Command Commands} to their previously saved sate.</li>
         * </ul>
         * @param actionEvent The triggering {@link java.awt.event.ActionEvent ActionEvent}.
		 */
		@SuppressWarnings("unchecked")
		@Override
		public void actionPerformed(ActionEvent actionEvent)
		{
			if (actionEvent.getActionCommand().equals(cbxEnvironments.getActionCommand()))
			{
				if (currentEnvironment != cbxEnvironments.getSelectedItem())
				{
					lblWorkflow.setEnabled(true);
					cbxWorkflows.setEnabled(true);
					
					updateCommand();
					promptSaveChanges();
					
					currentEnvironment = (Environment) cbxEnvironments.getSelectedItem();
					workflows.removeAllElements();
					if (currentEnvironment != null)
					{
						for (Workflow workflow : currentEnvironment.getWorkflows())
						{
							workflows.addElement(workflow);
						}
					}
					workflows.setSelectedItem(null);
					
					currentWorkflow = null;
					currentCommand = null;
					commands.clear();
					
					updateCommand();
				}
			}
			else if (actionEvent.getActionCommand().equals(cbxWorkflows.getActionCommand()))
			{
				if (currentWorkflow != ((JComboBox<Workflow>) actionEvent.getSource()).getSelectedItem())
				{					
					updateCommand();
					promptSaveChanges();
					
					currentWorkflow = (Workflow) ((JComboBox<Workflow>) actionEvent.getSource()).getSelectedItem();
					commands.clear();
					if (currentWorkflow != null)
					{
						for (Command command : currentWorkflow.getCommands())
						{
							commands.addElement(command);
						}
					}
					lstCommands.setSelectedIndex(-1);
					currentCommand = null;
	
					updateCommand();
				}
			}
			else if (actionEvent.getActionCommand().equals(btnAdd.getActionCommand()))
			{
				setEditedState(true);
				
				currentCommand = new Command();
				
				commands.insertElementAt(currentCommand, lstCommands.getSelectedIndex() + 1);
				lstCommands.setSelectedIndex(lstCommands.getSelectedIndex() + 1);
				lstCommands.ensureIndexIsVisible(lstCommands.getSelectedIndex());
				
				cbxActions.setSelectedIndex(0);
				pnlCommandArguments.setArguments(currentCommand.getArguments());
				txtDescription.setText(currentCommand.getDescription());
				
				updateCommand();
			}
			else if (actionEvent.getActionCommand().equals(btnRemove.getActionCommand()))
			{
				if (currentCommand != null)
				{
					setEditedState(true);
					
					int currentIndex = lstCommands.getSelectedIndex();
					commands.removeElement(currentCommand);

					// DefaultListModel does not support toArray(T[]).
					Command[] newCommands = new Command[commands.getSize()];
					commands.copyInto(newCommands);
					currentWorkflow.setCommands(new ArrayList<Command>(Arrays.asList(newCommands)));
					
					List<Workflow> workflows = currentEnvironment.getWorkflows();
					workflows.set(workflows.indexOf(currentWorkflow), currentWorkflow);
					currentEnvironment.setWorkflows(workflows);
					
					currentCommand = null;
					updateCommand();
					lstCommands.setSelectedIndex(currentIndex);
				}
			}
			else if (actionEvent.getActionCommand().equals(btnCopy.getActionCommand()))
			{
				copiedCommand = new Command(currentCommand);
				
				btnPaste.setEnabled(true);
				btnPaste.setToolTipText("Paste the " + copiedCommand.toString() + " command");
			}
			else if (actionEvent.getActionCommand().equals(btnPaste.getActionCommand()))
			{
				setEditedState(true);
				
				Command pastedCommand = new Command(copiedCommand);

				commands.insertElementAt(pastedCommand, lstCommands.getSelectedIndex() + 1);
				lstCommands.setSelectedIndex(lstCommands.getSelectedIndex() + 1);
				
				updateCommand();
			}
			else if (actionEvent.getActionCommand().equals(cbxActions.getActionCommand()))
			{
				if (currentCommand != null && currentCommand.getAction() != cbxActions.getSelectedItem())
				{
					currentCommand.setArguments(new ArrayList<String>());
				}
				
				updateCommandArgumentsPanel();
				updateCommand();
			}
			else if (actionEvent.getActionCommand().equals(btnRevert.getActionCommand()))
			{
				load();
			}
			else if (actionEvent.getActionCommand().equals(btnSave.getActionCommand()))
			{
				save();
			}
		}
	}

	/**
	 * {@link javax.swing.event.ListSelectionListener ListSelectionListener} for the following {@link userInterface.panels.PanelCommands PanelCommands} items:
     * <ul>
     *     <li>{@link settings.Command Command} {@link javax.swing.JList JList}</li>
     * </ul>
	 * @author BDS
	 */
	private class CommandListSelectionListener implements ListSelectionListener
	{
		/**
		 * Updates the UI with the selected {@link settings.Command Command's} information.
		 * @param listSelectionEvent The triggering {@link javax.swing.event.ListSelectionEvent ListSelectionEvent}.
		 */
		@Override
		public void valueChanged(ListSelectionEvent listSelectionEvent)
		{
			updateCommand();

			if (currentCommand != null)
			{
				cbxActions.setSelectedItem(currentCommand.getAction());
				pnlCommandArguments.setArguments(currentCommand.getArguments());
			}
		}
	}
	
	/**
     * {@link javax.swing.TransferHandler TransferHandler} for the following {@link userInterface.panels.PanelCommands PanelCommands} items:
     * <ul>
     *     <li>{@link settings.Command Command} {@link javax.swing.JList JList}</li>
     * </ul>
     * @author BDS
     */
	private class CommandTransferHandler extends TransferHandler
	{
		private static final long serialVersionUID = 1L;

		/**
		 * Determines if an operation is allowed.
		 * @param transferSupport The contextual {@link javax.swing.TransferHandler.TransferSupport TransferSupport} details regarding the operation.
		 * @return Always <code>true</code>; all operations are supported.
		 */
		public boolean canImport(TransferSupport transferSupport)
		{
			return true;
		}
		
		/**
		 * Captures the dragged item.
		 * @param component The parent {@link javax.swing.JComponent JComponent} housing the dragged item.
		 * @return The dragged {@link settings.Command Command} as a {@link java.awt.datatransfer.Transferable Transferable}.
		 */
	    @SuppressWarnings("unchecked")
		protected Transferable createTransferable(JComponent component)
	    {
	    	return ((JList<Command>) component).getSelectedValue();
	    }
	    
	    /**
	     * Determines what types of transfers are available.
         * @param component The parent {@link javax.swing.JComponent JComponent} housing the dragable items.
         * @return <ul>
         *             <li>The types of available {@link javax.swing.TransferHandler TransferHandlers}.</li>
         *             <li>Always returns {@link javax.swing.TransferHandler#MOVE TransferHandler.MOVE}.</li>
         *         </ul>
	     */
	    public int getSourceActions(JComponent component)
	    {
	        return TransferHandler.MOVE;
	    }
		
	    /**
	     * Performs the operation.
         * @param transferSupport The contextual {@link javax.swing.TransferHandler.TransferSupport TransferSupport} details regarding the operation.
	     * @return <ul>
         *             <li>The success of the operation.</li>
         *             <li>Always returns <code>true</code>; successes are implicit.</li>
         *         </ul>
	     */
		@SuppressWarnings("unchecked")
		public boolean importData(TransferSupport transferSupport)
		{
			try
			{
				setEditedState(true);
				
				Command dragCommand = (Command) transferSupport.getTransferable().getTransferData(DataFlavor.stringFlavor);
				
				int previousIndex = ((JList<Command>) transferSupport.getComponent()).getSelectedIndex();
				int newIndex = ((JList.DropLocation) transferSupport.getDropLocation()).getIndex();
				if (newIndex > previousIndex)
				{
					newIndex--;
				}

				((DefaultListModel<Command>) ((JList<Command>) transferSupport.getComponent()).getModel()).removeElement(dragCommand);
				((DefaultListModel<Command>) ((JList<Command>) transferSupport.getComponent()).getModel()).insertElementAt(dragCommand, newIndex);
				
				((JList<Command>) transferSupport.getComponent()).setSelectedIndex(newIndex);
				((JList<Command>) transferSupport.getComponent()).ensureIndexIsVisible(((JList<Command>) transferSupport.getComponent()).getSelectedIndex());
				
				updateCommand();
			}
			catch (Exception exception)
			{
				Logger.logException(exception);
			}
			
			return true;
		}
	}
	

	/**
     * {@link java.awt.event.ActionListener ActionListener} for {@link userInterface.panels.CommandArgumentPanels.PanelCommandArguments PanelCommandArguments}
     * {@link javax.swing.JComboBox JComboBoxes}.
     * @author BDS
     */
	private class CommandArgumentsActionListener implements ActionListener
	{
		/**
		 * Updates the selected {@link settings.Command Command}.
		 * @param actionEvent The triggering {@link java.awt.event.ActionEvent ActionEvent}.
		 */
		@Override
		public void actionPerformed(ActionEvent actionEvent)
		{
			updateCommand();
		}
	}
	
	/**
     * {@link java.awt.event.FocusAdapter FocusAdapter} for {@link userInterface.panels.CommandArgumentPanels.PanelCommandArguments PanelCommandArguments}
     * {@link javax.swing.JFormattedTextField JFormattedTextFields}.
     * @author BDS
     */
	private class CommandArgumentsFocusAdapter extends FocusAdapter
	{
	    /**
         * Updates the selected {@link settings.Command Command}.
         * @param focusEvent The triggering {@link java.awt.event.FocusEvent FocusEvent}.
         */
		@Override
		public void focusLost(FocusEvent focusEvent)
		{
			updateCommand();
		}
	}
	
	/**
     * {@link java.awt.event.KeyAdapter KeyAdapter} for {@link userInterface.panels.CommandArgumentPanels.PanelCommandArguments PanelCommandArguments}
     * {@link javax.swing.JFormattedTextField JFormattedTextFields}.
     * @author BDS
     */
	private class CommandArgumentsKeyAdapter extends KeyAdapter
	{
		/**
		 * Clears focus when the Enter key is pressed, which will update the selected {@link settings.Command Command}.
		 * @param keyEvent The triggering {@link java.awt.event.KeyEvent KeyEvent}.
		 */
		@Override
		public void keyTyped(KeyEvent keyEvent)
		{
			if (keyEvent.getKeyChar() == '\n')
			{
				resetFocus();
			}
			
			setEditedState(true);
		}
	}
	
	/**
     * {@link javax.swing.event.ChangeListener ChangeListener} for {@link userInterface.panels.CommandArgumentPanels.PanelCommandArguments PanelCommandArguments}
     * {@link javax.swing.JSpinner JSpinners}.
     * @author BDS
     */
	private class CommandArgumentsChangeListener implements ChangeListener
	{
	    /**
         * Updates the selected {@link settings.Command Command}.
         * @param focusEvent The triggering {@link javax.swing.event.ChangeEvent ChangeEvent}.
         */
		@Override
		public void stateChanged(ChangeEvent changeEvent)
		{
			updateCommand();
		}
	}
}
